﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="OptionMapper.cs" company="Matthias Friedrich">
//   Copyright © Matthias Friedrich 2013
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace CommandlineHandling
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;

    public class OptionMapper : ICommandLineOptionMapper
    {
        public static readonly OptionMapper DefaultMapper = new OptionMapper();

        public OptionMapper()
        {
        }

        public void MapObject(ICommandLineOption option)
        {
            this.MapObject(option, option);
        }

        public void MapObject(ICommandLineOption option, object instance)
        {
            ExceptionExtensions.ThrowArgumentNullException(option, () => option);
            ExceptionExtensions.ThrowArgumentNullException(instance, () => instance);

            Type t = instance.GetType();

            MapObjectMembers(option, instance, t.GetProperties);
            MapObjectMembers(option, instance, t.GetFields);
        }

        public T MapObject<T>(ICommandLineOption option) where T : new()
        {
            var instance = new T();

            this.MapObject(option, instance);

            return instance;
        }

        protected static Type GetMemberType(MemberInfo member)
        {
            if (member is PropertyInfo)
            {
                return ((PropertyInfo)member).PropertyType;
            }
            else if (member is FieldInfo)
            {
                return ((FieldInfo)member).FieldType;
            }

            return typeof(object);
        }

        protected static object GetMemberValue(ICommandLineOption option, MemberInfo member, OptionParameterAttribute next)
        {
            Type t = GetMemberType(member);
            return option.Parameter(next.ParameterName).GetValue(t);
        }

        protected static bool IsTypeOf<T>(MemberInfo member)
        {
            if (member is PropertyInfo)
            {
                return ((PropertyInfo)member).PropertyType == typeof(T);
            }
            else if (member is FieldInfo)
            {
                return ((FieldInfo)member).FieldType == typeof(T);
            }

            return false;
        }

        protected static void SetMemberValue(object instance, MemberInfo member, object value)
        {
            if (member is PropertyInfo)
            {
                ((PropertyInfo)member).SetValue(instance, value, null);
            }
            else if (member is FieldInfo)
            {
                ((FieldInfo)member).SetValue(instance, value);
            }
        }

        private static void MapObjectMembers(ICommandLineOption option, object instance, Func<IEnumerable<MemberInfo>> members)
        {
            foreach (MemberInfo member in members())
            {
                IEnumerable<OptionParameterAttribute> parameterAttributes = member.ReflectAttributes<OptionParameterAttribute>(true);
                foreach (OptionParameterAttribute next in parameterAttributes)
                {
                    bool hasParameter = option.Parameters.Any((x) => x.Name == next.ParameterName);

                    if (hasParameter)
                    {
                        object value = GetMemberValue(option, member, next);
                        SetMemberValue(instance, member, value);
                        break;
                    }
                    else if (!hasParameter && next.Required)
                    {
                        throw new MissingOptionException(string.Format("The \"{0}\"-parameter is missing.", next.ParameterName));
                    }
                }
            }
        }
    }
}